﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Collections.Concurrent;
using System.Net.Sockets;
using System.Diagnostics;
using SimpleTcpServer.WebSocketProtocols;
using System.Threading;

namespace SimpleTcpServer
{
    /// <summary>
    /// Inherits from TcpServer, start exe when got a connection (client) from TcpServer.
    /// Wrap TcpClient in a Connection object, manage list of connection objects
    /// Raise events when someone connecting, connected, disconnected, msg come, msg sent
    /// </summary>
    public class WebSocketServer : TcpServer, IDisposable
    {
        //private static Thread[] ClientThreads = new Thread[Environment.ProcessorCount];
        //private static Thread CleanupThread;

        //private static CancellationTokenSource cancellation = new CancellationTokenSource();

        //private static ConcurrentQueue<Context> ContextQueue { get; set; }
        //private static Dictionary<Context, WebSocketServer> ContextMapping { get; set; }

        private List<Connection> CurrentConnections = new List<Connection>();

        //static WebSocketServer()
        //{
            //ContextQueue = new ConcurrentQueue<Context>();
            //ContextMapping = new Dictionary<Context, WebSocketServer>();
            //CurrentConnections = new List<Connection>();

            //CleanupThread = new Thread(HandleContextCleanupThread);
            //CleanupThread.Name = "WebSocketServer Cleanup Thread";
            //CleanupThread.Start();

            //for (int i = 0; i < ClientThreads.Length; i++)
            //{
            //    ClientThreads[i] = new Thread(HandleClientThread);
            //    ClientThreads[i].Name = "WebSocketServer Client Thread #" + (i + 1);
            //    ClientThreads[i].Start();
            //}
        //}

        //private static void HandleClientThread()
        //{
        //    while (!cancellation.Token.IsCancellationRequested)
        //    {
        //        Context context;
        //        while (ContextQueue.Count == 0)
        //        {
        //            Thread.Sleep(10);
        //            if (cancellation.Token.IsCancellationRequested) return;
        //        }

        //        if (!ContextQueue.TryDequeue(out context))
        //        {
        //            continue;
        //        }

        //        Debug.WriteLine(string.Format("HandleClientThread, count current connections,  setup context."));

        //        lock (ContextMapping)
        //        {
        //            WebSocketServer client = ContextMapping[context];
        //            client.SetupContext(context);
        //        }

        //        lock (CurrentConnections)
        //        {
        //            CurrentConnections.Add(context);
        //        }
        //    }
        //}

        //private static void HandleContextCleanupThread()
        //{
        //    while (!cancellation.IsCancellationRequested)
        //    {
        //        Thread.Sleep(100);

        //        List<Context> currentConnections = new List<Context>();

        //        lock (CurrentConnections)
        //        {
        //            currentConnections.AddRange(CurrentConnections);
        //        }

        //        foreach (var connection in currentConnections)
        //        {
        //            if (cancellation.IsCancellationRequested) break;

        //            if (!connection.Connected)
        //            {
        //                lock (CurrentConnections)
        //                {
        //                    CurrentConnections.Remove(connection);
        //                }

        //                lock (ContextMapping)
        //                {
        //                    ContextMapping.Remove(connection);
        //                }

        //                connection.Handler.UnregisterContext(connection);

        //                connection.Dispose();
        //            }
        //        }
        //    }
        //}

        ///// <summary>
        ///// This is the Flash Access Policy Server. It allows us to facilitate flash socket connections much more quickly in most cases.
        ///// Don't mess with it through here. It's only public so we can access it later from all the IOCPs.
        ///// </summary>
        //internal AccessPolicyServer AccessPolicyServer;

        public event EventHandler<ConnectionEventArgs> ClientConnecting;
        public event EventHandler<ConnectionEventArgs> ClientConnected;
        public event EventHandler<ConnectionEventArgs> ClientDisconnecting;
        public event EventHandler<MessageEventArgs> MessageReceived;
        public event EventHandler<MessageEventArgs> MessgeSent;

        protected internal void OnClientConnecting(ConnectionEventArgs e)
        {
            if(ClientConnecting !=null)
                ClientConnecting(this, e);
        }

        protected internal void OnClientConnected(ConnectionEventArgs e)
        {
            if (ClientConnected != null)
                ClientConnected(this, e);
        }

        protected internal void OnClientDisconnecting(ConnectionEventArgs e)
        {
            if (ClientDisconnecting != null)
                ClientDisconnecting(this, e);
        }

        protected internal void OnMessageReceived(MessageEventArgs e)
        {
            if (MessageReceived != null)
                MessageReceived(this, e);
        }

        protected internal void OnMessgeSent(MessageEventArgs e)
        {
            if (MessgeSent != null)
                MessgeSent(this, e);
        }
        
        
        ///// <summary>
        ///// Enables or disables the Flash Access Policy Server(APServer).
        ///// This is used when you would like your app to only listen on a single port rather than 2.
        ///// Warning, any flash socket connections will have an added delay on connection due to the client looking to port 843 first for the connection restrictions.
        ///// </summary>
        //public bool FlashAccessPolicyEnabled = true;

        /// <summary>
        /// Configuration for the above heartbeat setup.
        /// TimeOut : How long until a connection drops when it doesn't receive anything.
        /// MaxPingsInSequence : A multiple of TimeOut for how long a connection can remain idle(only pings received) before we kill it.
        /// </summary>
        public TimeSpan TimeOut = TimeSpan.FromMinutes(1);

        /// <summary>
        /// A list of acceptable subprotocols that this server supports.
        /// See http://tools.ietf.org/html/rfc6455#section-1.9
        /// </summary>
        public string[] SubProtocols;

        private string _destination = String.Empty;
        private string _origin = String.Empty;

        /// <summary>
        /// Initializes a new instance of the <see cref="WebSocketServer"/> class.
        /// </summary>
        public WebSocketServer(int listenPort = 0, IPAddress listenAddress = null) : base(listenPort, listenAddress) { }

        /// <summary>
        /// Gets or sets the origin host.
        /// </summary>
        /// <value>
        /// The origin host.
        /// </value>
        public string Origin
        {
            get { return _origin; }
            set
            {
                _origin = value;
                AuthenticationBase.Origin = value;
            }
        }

        /// <summary>
        /// Gets or sets the destination host.
        /// </summary>
        /// <value>
        /// The destination host.
        /// </value>
        public string Destination
        {
            get { return _destination; }
            set
            {
                _destination = value;
                AuthenticationBase.Destination = value;
            }
        }

        /// <summary>
        /// Starts this instance.
        /// </summary>
        public override void Start()
        {
            base.Start();
            //if (AccessPolicyServer == null)
            //{
            //    AccessPolicyServer = new AccessPolicyServer(ListenAddress, Origin, Port);

            //    if (FlashAccessPolicyEnabled)
            //    {
            //        AccessPolicyServer.Start();
            //    }
            //}
            //Debug.WriteLine(string.Format("WebSocketServer started"));
        }

        /// <summary>
        /// Stops this instance.
        /// </summary>
        public override void Stop()
        {
            //if ((AccessPolicyServer != null) && (FlashAccessPolicyEnabled))
            //{
            //    AccessPolicyServer.Stop();
            //    AccessPolicyServer = null;
            //}
            base.Stop();
        }

        /// <summary>
        /// Fires when a client connects.
        /// </summary>
        /// <param name="data">The TCP Connection.</param>
        public override void OnConnectionArrive(object data)
        {
            var connection = new Connection(this, (TcpClient)data, new WebSocketProtocolFactory());
            Debug.WriteLine(string.Format("Created context to execute connection, client address {0}", connection.TcpClient.Client.RemoteEndPoint));
            //context.UserContext.ClientAddress = context.Connection.Client.RemoteEndPoint;
            //context.UserContext.SetOnConnect(OnConnect);
            //context.UserContext.SetOnConnected(OnConnected);
            //context.UserContext.SetOnDisconnect(OnDisconnect);
            //context.UserContext.SetOnSend(OnSend);
            //context.UserContext.SetOnReceive(OnReceive);
            connection.BufferSize = BufferSize;
            OnClientConnecting(new ConnectionEventArgs(connection));
            Debug.WriteLine(string.Format("Raised client connecting event"));


            ThreadPool.QueueUserWorkItem(SetupConnection, connection);
            //if (context.Connected)
            //{
            //    lock (ContextMapping)
            //    {
            //        ContextMapping[context] = this;
            //    }

            //    ContextQueue.Enqueue(context);
            //}
        }

        void SetupConnection(object data)
        {
            var connection = (Connection)data;
            connection.Setup();
            if (connection.Connected)
            {
                lock (CurrentConnections)
                {
                    CurrentConnections.Add(connection);
                }
            }
        }

        internal void RemoveConnection(Connection con)
        {
            lock (CurrentConnections)
            {
                CurrentConnections.Remove(con);
                Debug.WriteLine(string.Format("removed a connection from server, address {0}", con.ClientAddress));
            }
        }

        ///// <summary>
        ///// The root receive event for each client. Executes in it's own thread.
        ///// </summary>
        ///// <param name="result">The Async result.</param>
        //private void DoReceive(IAsyncResult result)
        //{
        //    var context = (Context)result.AsyncState;
        //    context.Reset();
        //    try
        //    {
        //        context.ReceivedByteCount = context.Connection.Client.EndReceive(result);
        //    }
        //    catch
        //    {
        //        context.ReceivedByteCount = 0;
        //    }

        //    if (context.ReceivedByteCount > 0)
        //    {
        //        context.Handler.HandleRequest(context);
        //        context.ReceiveReady.Release();
        //    }
        //    else
        //    {
        //        context.Disconnect();
        //        context.ReceiveReady.Release();
        //    }
        //}

        //private void SetupContext(Context _context)
        //{
        //    _context.ReceiveEventArgs.UserToken = _context;
        //    _context.ReceiveEventArgs.Completed += ReceiveEventArgs_Completed;
        //    _context.ReceiveEventArgs.SetBuffer(_context.Buffer, 0, _context.Buffer.Length);
        //    Debug.WriteLine(string.Format("Done setup context, starting receive"));
        //    StartReceive(_context);
        //}

        //private void StartReceive(Context _context)
        //{
        //    try
        //    {
        //        if (_context.ReceiveReady.Wait(TimeOut, cancellation.Token))
        //        {
        //            try
        //            {
        //                if (!_context.Connection.Client.ReceiveAsync(_context.ReceiveEventArgs))
        //                {

        //                    ReceiveEventArgs_Completed(_context.Connection.Client, _context.ReceiveEventArgs);
        //                }
        //            }
        //            catch (SocketException ex)
        //            {
        //                //logger.Error("SocketException in ReceieveAsync", ex);
        //                _context.Disconnect();
        //            }
        //        }
        //        else
        //        {
        //            //logger.Error("Timeout waiting for ReceiveReady");
        //            _context.Disconnect();
        //        }
        //    }
        //    catch (OperationCanceledException) { }
        //}

        //void ReceiveEventArgs_Completed(object sender, SocketAsyncEventArgs e)
        //{
        //    Debug.WriteLine(string.Format("Event ReceiveEventArgs_Completed raised, received some data"));
        //    var context = (Context)e.UserToken;
        //    context.Reset();
        //    if (e.SocketError != SocketError.Success)
        //    {
        //        //logger.Error("Socket Error: " + e.SocketError.ToString());
        //        context.ReceivedByteCount = 0;
        //    }
        //    else
        //    {
        //        context.ReceivedByteCount = e.BytesTransferred;
        //    }

        //    Debug.WriteLine(string.Format("Use the connection handler to handle request, then start receiving async again."));

        //    if (context.ReceivedByteCount > 0)
        //    {
        //        context.Handler.HandleRequest(context);
        //        context.ReceiveReady.Release();
        //        StartReceive(context);
        //    }
        //    else
        //    {
        //        context.Disconnect();
        //        context.ReceiveReady.Release();
        //    }
        //}

        public void Dispose()
        {
            //cancellation.Cancel();
            base.Dispose();
            //Handler.Instance.Dispose();
        }


        public void Broadcast(string message)
        {
            if (CurrentConnections != null)
            {
                foreach (var c in CurrentConnections)
                {
                    c.Send(message);
                }
            }
        }
    }
}
